// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using CIP4.Common;
using CIP4.RulesEngine;

namespace CIP4.Simple
{
    public abstract class DataProvider
    {
        protected CIP4ConnectionAttribute DataProviderConnection;

        protected DataProvider (CIP4ConnectionAttribute dataProviderConnection)
        {
            DataProviderConnection = dataProviderConnection;
        }

        public SerializerKindEnum SerializerKind
        {
            get { return DataProviderConnection.SerializerKind; }
        }

        private static readonly Type[] DataProviderConstructorParameterTypes = new[]
        {
            typeof(CIP4ConnectionAttribute),
            typeof(IDataProvider),
            typeof(LoggerDelegate)
        };

        public static IDataProvider GetProvider(CIP4ConnectionAttribute dataProviderConnection, IDataProvider cachingProvider, LoggerDelegate logger)
        {
            if (dataProviderConnection == null)
            {
                Globals.LoggerWrapper(MajorEnum.Error, MinorEnum.DataProviderConnectionMissing,
                    "GetProvider called with no DataProviderConnection: {0}", new object[] { Environment.StackTrace }, logger);
                return null;
            }
            try
            {
                Assembly providerAssembly = Globals.AssemblyResolve("CIP4.DataProvider." + dataProviderConnection.ProviderKind);
                if (providerAssembly == null)
                    throw new ArgumentException(string.Format("Unable to find assembly for CIP4.DataProvider.{0} - looked in {1}", dataProviderConnection.ProviderKind, Globals.GetAssemblyPathsString()));
                Type dataProvider = providerAssembly.GetType("CIP4.DataProvider." + dataProviderConnection.ProviderKind + ".DataProvider");
                ConstructorInfo constructor = dataProvider.GetConstructor (DataProviderConstructorParameterTypes);
                if (constructor == null)
                {
                    // GetConstructor() returns null if it can't find a matching constructor.
                    // http://msdn.microsoft.com/en-us/library/h93ya84h.aspx
                    throw new ApplicationException (string.Format ("There was no public {0} constructor that accepts the {1} required parameter types.", dataProvider.FullName, DataProviderConstructorParameterTypes.Length));
                }
                IDataProvider provider = (IDataProvider)constructor.Invoke(new object[] { dataProviderConnection, cachingProvider, logger });
                logger(MajorEnum.Trace, MinorEnum.LoadedProvider, "Loaded provider {0}", new object[] { dataProviderConnection });
                return provider;
            }
            catch (Exception ex)
            {
                Globals.LoggerWrapper(MajorEnum.Error, MinorEnum.ErrorLoadingProvider, 
                    "Error loading connection {0}: {1}", new object[] { dataProviderConnection, ex }, logger);
                return null;
            }
        }

        #region Common routines used by all providers

        protected static string UsesExternalIdInSearch(IBooleanValue searchExpression)
        {
            return string.Empty;
        }

        public static bool CompareProperties(IBooleanValue selectionExpression, object item, ISerializerType serializer)
        {
            if (item == null)
                return false;
            if (selectionExpression == null)
                return true;
            ItemDataPacketType dp = new ItemDataPacketType { Serializer = serializer };
            dp.Bind("item", item);
            return selectionExpression.Value(dp);
        }

        protected bool SetHeaderProperties(object itemObject, CollectionType collection, ISerializerType serializer)
        {
            if (serializer.GetExternalID(itemObject) == null)
            {
                Globals.LoggerWrapper(MajorEnum.Error, MinorEnum.ExternalIDMustHaveAValue, 
                    "ExternalID must have a value: {0}", new object[] { serializer.GetExternalID(itemObject) }, serializer.Logger);
                return false;
            }

            return true;
        }

        public bool IsACache { get; set; }

        protected bool LoggerWrapper(MajorEnum status, MinorEnum minorStatus, string message, object[] paras, LoggerDelegate logger)
        {
            if (IsACache && status == MajorEnum.Information)
                return true;
            return Globals.LoggerWrapper(status, minorStatus, message, paras, logger);
        }

        protected string GetExternalIdValueFromSelectionExpression(IBooleanValue selectionExpression, ISerializerType serializer)
        {
            string externalIdName = serializer.GetKeys().Count(k => k.Value == 0) == 1 ? serializer.GetKeys().FirstOrDefault().Key : null;

            string lookForExternalId = selectionExpression == null || externalIdName == null ? null :
                selectionExpression.ComparesTo(externalIdName).FirstOrDefault();

            if (string.IsNullOrEmpty(lookForExternalId))
                lookForExternalId = selectionExpression == null ? null :
                selectionExpression.ComparesTo("ExternalID").FirstOrDefault();

            return lookForExternalId;
        }

        public abstract IEnumerable<object> GetItems(CollectionType collection, IBooleanValue selectionExpression, CursorType cursor);

        public abstract void PurgeItems(IEnumerable<ExternalIDType> externalIDs, CollectionType collection, GetSerializerDelegate getSerializer);

        public virtual void PurgeItems(IBooleanValue selectionExpression, CollectionType collection, CursorType cursor)
        {

            LoggerDelegate saveLogger = cursor.Logger;
            cursor.Logger = 
                delegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
                {
                    if (!(status == MajorEnum.Information && minorStatus == MinorEnum.GetItems))
                        return saveLogger(status, minorStatus, message, paras);
                    return LoggerResponseEnum.keepGoing;
                };
            
            ISerializerType serializer = cursor.GetSerializer(SerializerKindEnum.CIP4Json);
            
            List<ExternalIDType> ids = new List<ExternalIDType>();
            
            foreach (object item in GetItems(collection, selectionExpression, cursor))
                ids.Add(serializer.GetExternalID(item));

            if (ids.Count == 0)
                LoggerWrapper(MajorEnum.Information, MinorEnum.PurgeItems,
                    "{0} items deleted from {1}", new object[] { 0, collection.Name }, serializer.Logger);
            else
                PurgeItems(ids, collection, cursor.GetSerializer);
            
            cursor.Logger = saveLogger;
        }

        #endregion

        protected class CollectionLockType : IDisposable
        {
            private readonly Mutex _dictionaryMutex;

            public CollectionLockType(string collectionID)
            {
                _dictionaryMutex = new Mutex(false, "Global\\" + CIP4Versions.CIPlVersionDomain + "." + collectionID + ".data");

                if (!_dictionaryMutex.WaitOne(new TimeSpan(0, 0, 60)))
                    throw new ApplicationException(String.Format("Unable to access dictionary {0}", collectionID));
            }

            #region IDisposable Members

            public void Dispose()
            {
                if (_dictionaryMutex != null)
                {
                    _dictionaryMutex.ReleaseMutex();
                    _dictionaryMutex.Close();
                }
            }

            #endregion
        }
    }
}
